﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;

namespace uk.ac.soton.ses.layouts
{
    internal class GraphData_Category
    {
        private Dictionary<string, Dictionary<string, string>> _baseGraphData;
        private List<string> _seriesStrings;
        private string _functionString;
        private bool _stackedGraph;
        private string _graphType;
        private List<string> _requestedParameters;

        public List<string> SeriesStrings { get { return this._seriesStrings; } set { this._seriesStrings = value; } }
        public string BaseFunctionString { get { return this._functionString; } set { this._functionString = value; } }

        public string XAxisTitle { get; set; }
        public string YAxisTitle { get; set; }
        public string ChartTitle { get; set; }

        public string FormattedFunctionString
        {
            get
            {
                if (this._functionString.Length > 0)
                {
                    return string.Format("function(chart) {{{0}}}", this._functionString);
                }
                else
                {
                    return "";
                }
            }
        }

        public string FormattedGraphType
        {
            get { return "type: '" + this._graphType + "'"; }
        }

        public string FormattedCategories
        {
            get
            {
                return "categories: ['" + string.Join("','", this._baseGraphData.Keys.ToArray()) + "']";
            }
        }

        public string FormattedPlotOptions
        {
            get
            {
                if (this._stackedGraph)
                {
                    return "plotOptions: { bar:{stacking:'normal'}, column:{stacking:'normal'} }";
                }
                else
                {
                    return "";
                }
            }
        }

        public string FormattedChartTitle
        {
            get
            {
                if (this.ChartTitle != null && this.ChartTitle.Length > 0)
                {
                    return string.Format("title: {{ text:'{0}'}}", this.ChartTitle);
                }
                else
                {
                    return "";
                }
            }
        }

        public string FormattedXAxisTitle
        {
            get
            {
                if (this.XAxisTitle != null && this.XAxisTitle.Length > 0)
                {
                    return string.Format("title: {{ text:'{0}'}}", this.XAxisTitle);
                }
                else
                {
                    return "";
                }
            }
        }

        public string FormattedYAxisTitle
        {
            get
            {
                if (this.YAxisTitle != null && this.YAxisTitle.Length > 0)
                {
                    return string.Format("title: {{ text:'{0}'}}", this.YAxisTitle);
                }
                else
                {
                    return "";
                }
            }
        }
        public string FormattedSeriesString
        {
            get
            {
                return "series: [" + string.Join(",", this._seriesStrings.ToArray()) + "]";
            }
        }

        public string FormattedJavaScript
        {
            get
            {
                string js = @"<script type='text/javascript'>
            $(function () {
                $('#graphcontainer').highcharts("
                +   "{"
                +       "chart: {"
                +           this.GetFirstStructure(this.FormattedGraphType)
                +       "}"
                +       ",xAxis: {" 
                +           this.GetFirstStructure(this.FormattedCategories)
                +           this.GetNextStructure(this.FormattedXAxisTitle)
                +       "}"
                +       ",yAxis: {"
                +           this.GetFirstStructure(this.FormattedYAxisTitle)
                +       "}"
                +       this.GetNextStructure(this.FormattedSeriesString)
                +       this.GetNextStructure(this.FormattedPlotOptions)
                +       this.GetNextStructure(this.FormattedChartTitle)
                +   "}"
                +   this.GetNextStructure(this.FormattedFunctionString)
                + @");
            });
            </script>";

                return js;
            }
        }

        /// <summary>
        /// At the moment, this just returns the string, but eventually it might add text like an opening brace.
        /// </summary>
        /// <param name="s">The string</param>
        /// <returns>The string</returns>
        public string GetFirstStructure(string s)
        {
            return s;
        }

        /// <summary>
        /// If the string is blank, an empty string is returned, otherwise a comma is prepended
        /// </summary>
        /// <param name="s">The string</param>
        /// <returns>The string with a comma prepended, or an empty string</returns>
        public string GetNextStructure(string s)
        {
            if (!string.IsNullOrEmpty(s))
            {
                return "," + s;
            }
            else
            {
                return "";
            }
        }

        public GraphData_Category(Dictionary<string, Dictionary<string, string>> graphData, string graphType, bool stackedGraph, List<string> requestedParameters)
        {
            //Store the data we have been passed
            this._baseGraphData = graphData;
            this._graphType = graphType;
            this._stackedGraph = stackedGraph;
            this._requestedParameters = requestedParameters;

            //Default these to something sensible
            this._functionString = "";
            this._seriesStrings = new List<string>();

            //Try and populate the function string and the series strings
            switch (this._graphType)
            {
                case "bar":
                case "column":
                    this.GetSeriesDataForBarGraph();
                    break;
                case "pie":
                    this.GetSeriesDataForPieGraph();
                    break;
                default:
                    throw new Exception("Incorrect graph type requested");
            }
        }

        private void GetSeriesDataForPieGraph()
        {
            int percentageSizeStep = 0;

            List<KeyValuePair<string, Dictionary<string, string>>> graphDataList = this._baseGraphData.ToList();

            List<string> seriesStrings = new List<string>();

            string additionalGraphFunctions = "";

            for (int i = 0; i < graphDataList.Count; i++)
            {
                string additionalSeriesOptions = "";
                List<string> parameter_values = new List<string>();

                if (i == 0)
                {
                    //Only calculate the percentage step once
                    percentageSizeStep = Convert.ToInt32(Math.Floor(100.0 / graphDataList.Count));

                    //Ensure we get a legend
                    additionalSeriesOptions += "showInLegend:true,";
                }

                KeyValuePair<string, Dictionary<string, string>> dataset = graphDataList[i];
                Dictionary<string, string> dataset_parameters = graphDataList[i].Value;

                foreach (string reqParam in this._requestedParameters)
                {
                    string newValuePair;
                    if (dataset_parameters.ContainsKey(reqParam))
                    {
                        newValuePair = string.Format("['{0}',{1}]", reqParam, dataset_parameters[reqParam]);
                    }
                    else
                    {
                        //If we don't have a value for a parameter, send null to the charting software so it knows we didn't have a value for this dataset
                        newValuePair = string.Format("['{0}',null]", reqParam);
                    }
                    parameter_values.Add(newValuePair);
                }

                //Calculate pie size. Note this is different for stacked vs non-stacked pies
                if (this._stackedGraph)
                {
                    if (i == 0)
                    {
                        additionalSeriesOptions += string.Format("innerSize:'{0}%',", 100 - (percentageSizeStep * (i + 1)));
                    }
                    else if (i == graphDataList.Count - 1)
                    {
                        additionalSeriesOptions += string.Format("size:'{0}%',", percentageSizeStep);
                    }
                    else
                    {
                        additionalSeriesOptions += string.Format("size:'{0}%',innerSize:'{1}%',", 100 - (percentageSizeStep * i), 100 - (percentageSizeStep * (i + 1)));
                    }
                }
                else
                {
                    //To calculate pie size for a non-stacked graph, we need to work out how many pies we need to draw

                    //We'll wrap once we get to 'maxcolsforonerow' * pies on a row
                    int maxcolsforonerow = 5;

                    //Calculate how many rows we will have
                    int totalrows = graphDataList.Count <= maxcolsforonerow ? 1 : 2;

                    //Calculate how many columns will be in each row
                    int columnsperrow = Convert.ToInt32(Math.Ceiling(graphDataList.Count / (double)totalrows));

                    //Keep a track of how many datasets we've looked at
                    int thisdatasetno = i + 1;

                    //Which row are we processing at the moment?
                    int thisrow = thisdatasetno <= columnsperrow ? 1 : 2;

                    //Which column are we processing at the moment?
                    int thiscolumn = thisdatasetno - ((thisrow - 1) * columnsperrow);

                    //The base sizes for calculating the size of this pie chart
                    int basesizeforonecol = 90;
                    int basesizefortwocol = 40;

                    //Calculate size
                    int size = totalrows > 1 ? basesizefortwocol : (basesizeforonecol - ((graphDataList.Count - 1) * 10));

                    //Calculate X centre, with pie centres being placed somewhere between startcentrex and endcentrex
                    double centrex;
                    int startcentrex = 10;
                    int endcentrex = 90;
                    int centrexdiff = endcentrex - startcentrex;
                    double piespacing = centrexdiff / (double)(columnsperrow - 1);

                    //We only have one pie per row, so it will go in the middle (50%)
                    if (columnsperrow == 1)
                    {
                        centrex = 50.0;
                    }
                    //We have two pies per row, so they will go at 25% and 75% (X coordinates)
                    else if (columnsperrow == 2)
                    {
                        centrex = thiscolumn == 1 ? 25.0 : 75.0;
                    }
                    //For all other cases, they will be spaced out between 'startcentrex' and 'endcentrex'
                    else
                    {
                        centrex = startcentrex + (piespacing * (thiscolumn - 1));
                    }

                    //Calculate Y centre
                    int centrey;

                    //With only 1 row, the pie will go in the middle (50%)
                    if (totalrows == 1)
                    {
                        centrey = 50;
                    }
                    //With two rows, the pie will go at 25% or 75% (Y coordinate)
                    else
                    {
                        centrey = thisrow == 1 ? 25 : 75;
                    }

                    additionalSeriesOptions += string.Format("size: '{0}%',center: ['{1}%' ,'{2}%' ],", size, centrex, centrey);

                    additionalGraphFunctions += string.Format("chart.renderer.text('{0}', chart.get('{0}').getCenter()[0]-100, '{1}').add();", dataset.Key, thisrow == 1 ? 60 : 350);
                }

                string series_data = string.Join(",", parameter_values.ToArray());
                string seriesString = string.Format("{{id: '{0}', name: '{0}', {1} data:[{2}], dataLabels:{{enabled:false}}}}", dataset.Key, additionalSeriesOptions, series_data);
                seriesStrings.Add(seriesString);
            }

            this.SeriesStrings = seriesStrings;
            this.BaseFunctionString = additionalGraphFunctions;
        }

        private void GetSeriesDataForBarGraph()
        {
            List<string> seriesStrings = new List<string>();

            List<KeyValuePair<string, Dictionary<string, string>>> graphDataList = this._baseGraphData.ToList();

            foreach (string reqParam in this._requestedParameters)
            {
                List<string> parameter_values = new List<string>();

                for (int i = 0; i < graphDataList.Count; i++)
                {
                    Dictionary<string, string> dataset_data = graphDataList[i].Value;
                    string newValuePair;
                    if (dataset_data.ContainsKey(reqParam))
                    {
                        newValuePair = string.Format("['{0}',{1}]", graphDataList[i].Key, graphDataList[i].Value[reqParam]);
                    }
                    else
                    {
                        newValuePair = "[]";
                    }
                    parameter_values.Add(newValuePair);
                }

                string series_data = string.Join(",", parameter_values.ToArray());
                string seriesString = string.Format("{{ name: '{0}', data: [{1}]}}", reqParam, series_data);
                seriesStrings.Add(seriesString);
            }

            this._seriesStrings = seriesStrings;
        }

    }
}
